淺談 .NET 預設 Logger 及其優化技巧
TLDR
.NET預設已整合 Console、Debug 與 EventSource 等 Provider,可透過builder.Logging進行自訂。- 使用
appsettings.json可靈活控制不同命名空間與 Provider 的日誌等級,支援萬用字元*。 - 針對高頻率或高資源消耗的日誌,應優先使用
logger.IsEnabled進行預先檢查,避免無效運算。 - 結構化日誌(Structured Logging)能避免字串串接效能損耗,並利於 ELK 等系統解析。
- 推薦使用
[LoggerMessage]Source Generator,它能自動產生強型別委派,消除object[]配置與 Boxing 成本,且內建IsEnabled檢查。
範例專案
本文的可執行範例:CloudyWing/DotNetLoggingSample。
紀錄等級
在開發與維護時,應根據情境選擇合適的 Log Level,以利於生產環境的過濾與除錯。
| 等級 | 值 | 方法 | 描述 |
|---|---|---|---|
| 追蹤 (Trace) | 0 | LogTrace | 最詳細訊息,預設停用,不應在生產環境啟用。 |
| 偵錯 (Debug) | 1 | LogDebug | 用於開發除錯,生產環境需謹慎使用。 |
| 資訊 (Information) | 2 | LogInformation | 追蹤應用程式一般流程。 |
| 警告 (Warning) | 3 | LogWarning | 處理異常或意外事件,但不影響程式運作。 |
| 錯誤 (Error) | 4 | LogError | 無法處理的例外情況。 |
| 重要 (Critical) | 5 | LogCritical | 需要立即處理的嚴重故障。 |
| None | 6 | 無 | 完全停用所有 LOG。 |
基本注入與設定
WebApplication.CreateBuilder() 預設已載入 Console、Debug 與 EventSource 等 Provider。若需調整,可透過 builder.Logging 進行設定。
什麼情況下會遇到這個問題:當你需要自訂日誌輸出目標或清除預設的 Provider 時。
csharp
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
// 清除預設 Provider 並重新設定
builder.Logging.ClearProviders();
builder.Logging.AddConsole();
builder.Logging.AddDebug();使用 Appsettings.json 控制日誌等級
透過設定檔可以針對不同命名空間(Category)設定日誌等級,實現細粒度的控制。
什麼情況下會遇到這個問題:當你希望在生產環境中隱藏特定 Service 的詳細日誌,僅保留 Warning 以上等級時。
json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"LoggerTest.Services.TestService1": "Warning"
}
}
}- 若需針對多個命名空間套用相同設定,可使用萬用字元,例如
"*.Services": "Warning"。
使用 Logger.IsEnabled 提高效能
在執行昂貴操作(如資料庫查詢)前,應先檢查該等級是否啟用。
什麼情況下會遇到這個問題:當日誌內容需要經過複雜計算或大量資料處理才能產出時。
csharp
if (logger.IsEnabled(LogLevel.Information)) {
int processedRecords = await database.GetProcessedRecordsCount();
logger.LogInformation("系統已完成資料更新,共處理 {Count} 筆資料。", processedRecords);
}結構化日誌的優勢
結構化日誌透過模板與參數分離,避免了執行期的字串串接,並能將資料以 JSON 格式輸出,方便後續分析。
什麼情況下會遇到這個問題:當你需要將日誌匯入 ELK 或其他集中式日誌分析系統時。
csharp
// 正確的結構化日誌寫法
logger.LogInformation("使用者 {UserId} 已登入系統", user.Id);
// 輸出 JSON 格式
builder.Logging.AddJsonConsole();使用 LoggerMessage.Define 與 Source Generator
傳統的 LogInformation 呼叫會產生 object[] 配置與 Boxing 成本。使用 [LoggerMessage] Source Generator 可在編譯期產生高效的強型別委派。
什麼情況下會遇到這個問題:當應用程式對效能要求極高,且有大量日誌輸出需求時。
使用 [LoggerMessage] 範例
csharp
public partial class UserService {
private readonly ILogger<UserService> logger;
public UserService(ILogger<UserService> logger) => this.logger = logger;
public void Login(User user) => LogUserLoggedIn(user.Id, user.Department);
[LoggerMessage(
EventId = 1001,
Level = LogLevel.Information,
Message = "使用者 {UserId} 已登入系統,所屬部門: {Department}"
)]
private partial void LogUserLoggedIn(string userId, string department);
}- 效能優勢:編譯期自動產生
IsEnabled檢查,呼叫路徑無object[]配置,且無 Boxing 成本。 - 命名建議:使用
Log開頭的動詞短句,等級由 Attribute 控制,參數名稱需與 Message 模板中的{Placeholder}一致。
異動歷程
- 初版文件建立。
- 新增「使用 LoggerMessage.Define」與「使用 LoggerMessage Source Generator」章節,並加入範例專案連結。